feat(webhooks): add verify_and_decode_webhook for compressed payloads#230
Open
nijeesh-stream wants to merge 2 commits intomasterfrom
Open
feat(webhooks): add verify_and_decode_webhook for compressed payloads#230nijeesh-stream wants to merge 2 commits intomasterfrom
nijeesh-stream wants to merge 2 commits intomasterfrom
Conversation
… (CHA-3071) Stream Chat backend can now compress outbound webhook payloads with gzip and, for SQS / SNS firehose delivery, base64-wrap the compressed bytes so they remain valid UTF-8 over the queue. Add two new client methods that let customers decompress + verify in a single call: - decompress_webhook_body(body, content_encoding=None, payload_encoding=None) primitive decode that handles gzip and/or base64 - verify_and_decode_webhook(body, x_signature, content_encoding=None, payload_encoding=None) decode + HMAC-SHA256 verify Both are exposed on the sync StreamChat and async StreamChatAsync clients through the shared StreamChatInterface base, mirroring the existing verify_webhook helper. The existing verify_webhook signature and behavior are unchanged for backward compatibility. A new WebhookSignatureError (extends StreamAPIException) is raised on signature mismatch, malformed gzip, or malformed base64. Unsupported encoding values raise ValueError with a message that points at the supported algorithm (gzip). The decoding logic lives in stream_chat/webhook.py so it can be tested without instantiating an HTTP client. The new tests cover the cross-SDK contract: passthrough, gzip round-trip, base64 round-trip, base64 + gzip (SQS / SNS shape), case-insensitive aliases, every unsupported content_encoding (br / brotli / zstd / deflate / compress / lz4), unsupported payload_encoding (hex / url / binary), invalid gzip / base64 input, and three signature-mismatch variants (wrong signature, signature over compressed bytes, signature over wrapped bytes). Docs: webhooks_overview.md gets a "Compressed webhook bodies" section with Django, Flask, and SQS / SNS usage examples. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Linear: CHA-3071 — Stream Chat backend can now compress outbound webhook payloads with gzip and, for SQS / SNS firehose delivery, base64-wrap the compressed bytes so they remain valid UTF-8 over the queue. This SDK now ships helpers so customers can decompress + verify in one call.
New API
Both methods live on the shared
StreamChatInterfacebase, so they are inherited by both the syncStreamChatand the asyncStreamChatAsyncclients (mirroring the existingverify_webhook):client.decompress_webhook_body(body, content_encoding=None, payload_encoding=None) -> bytes— primitive decode with no signature check.client.verify_and_decode_webhook(body, x_signature, content_encoding=None, payload_encoding=None) -> bytes— convenience method: decode + HMAC-SHA256 verify, returns the raw JSON body as bytes (json.loadsaccepts bytes directly).payload_encoding="base64"(or"b64") is for the SQS / SNS firehose envelope.content_encoding="gzip"is for HTTP webhooks withContent-Encoding: gzip.None/""for either is a no-op, so the regular HTTP webhook path is bytewise identical to today.A new exception
WebhookSignatureError(StreamAPIException)is raised on signature mismatch, malformed gzip, or malformed base64. Unsupported encoding values raiseValueErrorwith a message that namesgzip(so customers know which compression algorithm to set on the app config).Backwards compatibility
verify_webhook(request_body, x_signature) -> boolsignature and behavior are unchanged.gzip,base64,hmac,hashlibare all stdlib.Why a separate
stream_chat/webhook.pyThe decoding logic is plain Python with no client state. Hosting it in a stand-alone module lets the tests exercise the cross-SDK contract (passthrough, gzip, base64, base64 + gzip, signature mismatch variants, unsupported encodings) without instantiating an HTTP client.
Docs
docs/webhooks/webhooks_overview/webhooks_overview.mdgets a new Compressed webhook bodies section with the standard "what / why / before you enable" copy plus Django, Flask, and SQS / SNS usage examples.Test plan
make lint— black, flake8, mypy all clean.pytest stream_chat/tests/test_webhook_compression.py -v— 46 passed (coversverify_webhookBC, everydecompress_webhook_bodyrule, everyverify_and_decode_webhookhappy path + signature-mismatch variant, both client methods, async happy path).make test— runs against a live Stream app (requiresSTREAM_KEY/STREAM_SECRET); will run on CI.Made with Cursor